<template>
  <div id="global-uploader" :class="{ 'global-uploader-single': !global }">
    <!-- 上传 -->
    <uploader
        ref="uploaderRef"
        class="uploader-app"
        :options="options"
        :file-status-text="fileStatusText"
        :auto-start="false"
        @file-added="onFileAdded"
        @file-success="handleFileSuccess"
        @file-error="handleFileError"
    >
      <!-- 选择按钮 在这里隐藏 -->
      <uploader-btn id="global-uploader-btn" :attrs="attrs" ref="uploadBtnRef">选择文件</uploader-btn>
      <uploader-btn id="global-uploader-btn" :attrs="attrs" :directory="true" ref="uploadDirBtn">选择目录</uploader-btn>


      <uploader-list v-show="panelShow">
        <template #default="{ fileList }">
          <div class="file-panel" :class="{ collapse: collapse }">
            <div class="file-title">
              <n-space class="title">文件列表</n-space>
              <div class="operate">
                <n-button size="small" :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
                  <n-icon :component="!collapse? ChevronDownSharp : ChevronUpOutline"/>
                </n-button>
                <n-button size="small" title="关闭" link @click="close">
                  <n-icon :component="CloseOutline"/>
                </n-button>
              </div>
            </div>
            <ul class="file-list" style="padding-left: 5px;margin-top: 5px;margin-bottom: 5px;margin-right: 5px;">
              <li
                  style="list-style:none;"
                  v-for="file in fileList"
                  :key="file.id"
                  class="file-item"
              >
                <uploader-file
                    ref="files"
                    :class="['file_' + file.id, customStatus]"
                    :file="file"
                    :list="true"
                ></uploader-file>
              </li>
              <div v-if="!fileList.length" class="no-file">
                <n-icon icon="ri:file-3-line" width="16"/>
                暂无待上传文件
              </div>
            </ul>
          </div>
        </template>
      </uploader-list>
    </uploader>
  </div>
</template>

<script>
/**
 *  全局上传插件，两种调用方式
 *   1. 作为全局页面的组件，使用event bus
 *   调用方法：Bus.emit('openUploader', {params: {}, options: {}})
 *               params: 发送给服务器的额外参数；
 *               options：上传选项，目前支持 target、testChunks、mergeFn、accept
 *
 *   监听函数：Bus.on('fileAdded', fn); 文件选择后的回调
 *           Bus.on('fileSuccess', fn); 文件上传成功的回调，监听后记得释放
 *
 */
import {ref, watch, computed, nextTick, onMounted} from 'vue'
import Bus from './utils/bus'
import {setting} from "@/config/setting";
import {useUserStore} from "@/store/modules/useUserStore";
import {store} from "@/store";
import {FilePanStore} from "@/store/modules/file";
import {storeToRefs} from "pinia";
import {ChevronDownSharp, ChevronUpOutline, CloseOutline, Videocam} from '@vicons/ionicons5'
import {generateMD5} from './utils/md5'
import fileFunction from "@/libs/globalFunction/file";
import {Icon} from "@iconify/vue";
import {useSystemStore} from "@/store/modules/useSystemStore";
import {storage} from "@/hooks/useStorage";


export default {
  name: 'GlobalUploader',
  components: {Icon},
  computed: {
    fileFunction() {
      return fileFunction
    }
  },
  emits: ['fileAdded', 'fileSuccess'],

  setup(props, {emit}) {
    const {
      getTheme,
    } = storeToRefs(useSystemStore());

    const filePanStore = FilePanStore()
    // 拖拽上传是否显示
    const dropBoxShow = ref(false)
    // 上传路径
    const {devBaseURL, prodBaseURL} = setting
    // 授权信息
    const {authorization,tokenKey} = storeToRefs(useUserStore(store));
    const token = authorization?.value ?? storage.get(tokenKey?.value);
    if (!token) {
      window.$message.warning('获取上传组件异常,请刷新页面后操作！！！！')
      return
    }
    const attrs = ref({
      accept: '*'
    })
    //  粘贴图片 File 对象
    let pasteImgObj = null
    // 粘贴图片的信息
    const pasteImg = {
      src: '',
      name: ''
    }

    let params = ref(null)
    let uploadWay = ref("1")
    let callType = ref("1")
 
    const options = {
      target: `${import.meta.env.DEV ? devBaseURL : prodBaseURL}/filetransfer/uploadfile`, // 上传文件-目标 URL
      chunkSize: 1024 * 1024, //  每个分片的大小
      fileParameterName: 'file', //  上传文件时文件的参数名，默认 file
      maxChunkRetries: 3, //  并发上传数，默认 3
      // testChunks: false, //  是否开启分片已存在于服务器的校验
      // 服务器分片校验函数，秒传及断点续传基础
      checkChunkUploadedByResponse: (chunk, message) => {
        let objMessage = JSON.parse(message)
        console.log('objmessage', objMessage)
        if (objMessage.code === 200) {
          let data = objMessage.data
          if (data.skipUpload) {
            // 分片已存在于服务器中
            return true
          }
          return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
        } else {
          console.log(objMessage.msg)
          return true
        }
      },
      headers: {
        Authorization: token
      },
      query() {

      }
    }

    const fileStatusText = {
      success: '上传成功',
      error: '上传失败',
      uploading: '上传中',
      paused: '已暂停',
      waiting: '等待上传'
    }

    // 剩余存储空间
    const remainderStorageValue = computed(() => {
      return filePanStore.remainderStorageValue;
    })
    // 上传文件数量
    let filesLength = 0
    const customStatus = ref('')
    const panelShow = ref(true)
    const collapse = ref(false)
    const uploaderRef = ref()
    const uploadBtnRef = ref()
    const uploadDirBtn = ref()
    const uploader = computed(() => uploaderRef.value?.uploader)

    async function onFileAdded(file) {
      panelShow.value = true
      trigger('fileAdded')
      Object.assign(uploader.value.opts, {
        query: {
          ...params.value
        }
      })
      const filesTotalSize = file.size;
      if (remainderStorageValue < filesTotalSize) {
        window.$message.error(`剩余存储空间不足，请重新选择${file.length > 1 ? '批量' : ''}文件`)
        file.ignored = true //  本次选择的文件过滤掉
      } else {
        // 批量或单个选择的文件未超出剩余存储空间，正常上传
        filesLength += file.length
        dropBoxShow.value = false
        panelShow.value = true
        collapse.value = false
        // computeMD5(file)
        const md5 = await computeMD5(file)
        startUpload(file, md5)
      }
    }

    /**
     * 计算md5，实现断点续传及秒传
     * @param {object} file 文件信息
     */
    const computeMD5 = (file) => {
      statusSet(file.id, 'md5')
      file.pause()
      // 计算MD5时隐藏”开始“按钮
      nextTick(() => {
        document.querySelector(`.file_${file.id} .uploader-file-resume`).style.display = 'none'
      })
      // 开始计算MD5
      return new Promise((resolve, reject) => {
        generateMD5(file, {
          onProgress(currentChunk, chunks) {
            // 实时展示MD5的计算进度
            nextTick(() => {
              const md5ProgressText = '校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
              console.log(md5ProgressText)
              document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
            })
          },
          onSuccess(md5) {
            statusRemove(file.id)
            resolve(md5)
          },
          onError() {
            error(`文件${file.name}读取出错，请检查该文件`)
            file.cancel()
            statusRemove(file.id)
            reject()
          }
        })
      })
    }

    // 图片粘贴事件
    const handlePaste = (e) => {
      let pasteItems = e.clipboardData.items
      if (pasteItems && pasteItems.length) {
        // 获取剪切板中最新的对象
        let imgObj = pasteItems[0].getAsFile()
        pasteImgObj =
            imgObj !== null
                ? new File(
                    [imgObj],
                    `qiwenshare_${new Date().valueOf()}.${
                        imgObj.name.split('.')[1]
                    }`,
                    {
                      type: imgObj.type
                    }
                )
                : null
      } else {
        window.$message.error('当前浏览器不支持')
        return false
      }
      if (!pasteImgObj) {
        window.$message.error('粘贴内容非图片')
        return false
      }
      pasteImg.name = pasteImgObj.name
      // 此时file就是剪切板中的图片对象
      // 如果需要预览，可以执行下面代码
      let reader = new FileReader()
      reader.onload = function (event) {
        pasteImg.src = event.target.result
      }
      reader.readAsDataURL(pasteImgObj)
    }


    // 上传粘贴的图片
    const handleUploadPasteImg = () => {
      uploaderRef.addFile(pasteImgObj) //  触发文件添加事件
    }

    // 删除粘贴的图片
    const handleDeletePasteImg = () => {
      pasteImg.src = ''
      pasteImg.name = ''
      pasteImgObj = null
    }

    // md5计算完毕，开始上传
    function startUpload(file, md5) {
      file.uniqueIdentifier = md5
      file.resume()
    }

    /**
     * 文件上传成功 回调函数
     * @param {object} rootFile 成功上传的文件所属的根 Uploader.File 对象，它应该包含或者等于成功上传文件
     * @param {object} file 当前成功的 Uploader.File 对象本身
     * @param {string} response 服务端响应内容，永远都是字符串
     */
    const handleFileSuccess = (rootFile, file, response) => {
      if (response == '') {
        file.statusStr = '上传失败'
        return
      }
      let result = JSON.parse(response)
      if (result.code === 200) {
        window.$message.success(`上传完毕`)
        trigger('fileSuccess', file)
        file.statusStr = ''
        if (filesLength === 1) {
          // 本次所有的文件均已上传完毕
          window.$message.success(`上传完毕`)
          // callType 调用此服务的方式：1 - 顶部栏，2 - 右键菜单
          trigger('fileSuccess')
          if (callType.value === 1) {
            trigger('fileSuccess')
            // props.serviceEl.$emit('getTableDataByType')
          } else {
            // props.serviceEl.getTableDataByType()
          }
          // props.serviceEl.$store.dispatch('showStorage')
        }
      } else {
        window.$message.error(result.msg)
        file.statusStr = '上传失败'
      }
      filesLength--
    }

    /**
     * 文件上传失败 回调函数
     * @param {object} rootFile 成功上传的文件所属的根 Uploader.File 对象，它应该包含或者等于成功上传文件
     * @param {object} file 当前成功的 Uploader.File 对象本身
     * @param {string} response 服务端响应内容，永远都是字符串
     */
    const handleFileError = (rootFile, file, response) => {
      window.$message.error(response || '上传失败')
    }


    function close() {
      uploader.value.cancel()
      panelShow.value = false
    }

    /**
     * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    function statusSet(id, status) {
      const statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }
      customStatus.value = status
      nextTick(() => {
        const statusTag = document.createElement('p')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc
        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        statusWrap.appendChild(statusTag)
      })
    }

    function statusRemove(id) {
      customStatus.value = ''
      nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
      })
    }

    function trigger(e, data) {
      Bus.emit(e, data)
      emit(e, data)
    }

    function error(msg) {
      window.$message.error(msg)

    }

    onMounted(() => {
      /**
       * 初始化上传组件实例
       * @param {string} params 上传文件组件参数
       * @param {object} uploadWay 上传方式 0-文件上传 1-文件夹上传 2-粘贴图片或拖拽上传
       * @param {} callType 是否组件本身
       */
      Bus.on('openUploader', ({params_ = {}, uploadWay_ = null, callType_ = null}) => {
        params = params_
        if (!token) {
          window.$message.warning('获取上传组件异常,请刷新页面后操作！！！！')
          return
        }
        uploadWay = uploadWay_
        callType = callType_
        // 确定上传方式
        if (uploadWay_ === '1') {
          uploadBtnRef.value.$el.click()
        } else if (uploadWay_ === '2') {
          uploadDirBtn.value.$el.click()
        } else {
          pasteImg.src = ''
          pasteImg.name = ''
          pasteImgObj = null
          dropBoxShow.value = true
        }
      })
    })

    return {
      fileStatusText,
      customStatus,
      panelShow,
      collapse,
      uploaderRef,
      uploadBtnRef,
      uploadDirBtn,
      attrs,
      ChevronDownSharp,
      ChevronUpOutline,
      CloseOutline,
      dropBoxShow,
      pasteImg,
      options,
      pasteImgObj,
      onFileAdded,
      handleFileSuccess,
      handleFileError,
      close,
      handlePaste,
      handleUploadPasteImg,
      handleDeletePasteImg,
      getTheme
    }
  }
}
</script>

<style>
#global-uploader {
  position: fixed;
  z-index: 20;
  right: 15px;
  bottom: 15px;
  box-sizing: border-box;
}

#global-uploader .uploader-app {
  width: 520px;
}

#global-uploader .file-panel {
  background-color: #fff;
  border: 1px solid #E2E2E2;
  border-radius: 7px 7px 0 0;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}

#global-uploader .file-panel .file-title {
  display: flex;
  height: 40px;
  line-height: 40px;
  padding: 0 15px;
  border-bottom: 1px solid #ddd;
}

#global-uploader .file-panel .file-title .operate {
  flex: 1;
  text-align: right;
}

#global-uploader .file-panel .file-title .operate .el-button {
  --el-button-hover-link-text-color: #0084ff;
}

#global-uploader .file-panel .file-title .operate .el-button + .el-button {
  margin-left: 8px;
}

#global-uploader .file-panel .file-list {
  position: relative;
  height: 240px;
  overflow-x: hidden;
  overflow-y: auto;
  background-color: #fff;
  transition: all 0.3s;
}

#global-uploader .file-panel .file-list .file-item {
  background-color: #fff;
}

#global-uploader .file-panel.collapse .file-title {
  background-color: #E7ECF2;
}

#global-uploader .file-panel.collapse .file-list {
  height: 0;
}

#global-uploader .no-file {
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #999;
}

#global-uploader .no-file svg {
  vertical-align: text-bottom;
}

#global-uploader .uploader-file.md5 .uploader-file-resume {
  display: none;
}

#global-uploader .uploader-file-icon:before {
  content: '' !important;
}

#global-uploader .uploader-file-icon[icon='image'] {
  background: url(./images/image-icon.png);
}

#global-uploader .uploader-file-icon[icon='audio'] {
  background: url(./images/audio-icon.png);
  background-size: contain;
}

#global-uploader .uploader-file-icon[icon='video'] {
  background: url(./images/video-icon.png);
}

#global-uploader .uploader-file-icon[icon='document'] {
  background: url(./images/text-icon.png);
}

#global-uploader .uploader-file-icon[icon='unknown'] {
  background: url(./images/zip.png) no-repeat center;
  background-size: contain;
}

#global-uploader .uploader-file-actions > span {
  margin-right: 6px;
}

#global-uploader .custom-status {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
}

/* 隐藏上传按钮 */
#global-uploader-btn {
  display: none;
}

</style>
